/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lvyh.lightframe.job.admin.util;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BeanUtil {
    /**
     * Converting a map object into a JavaBean
     */
    public static <T> T toBean(Class<T> type, Map map)
            throws IntrospectionException, IllegalAccessException,
            InstantiationException, InvocationTargetException {
        BeanInfo beanInfo = Introspector.getBeanInfo(type); // Get class properties
        T obj = type.newInstance(); // Creating JavaBean objects

        // Assigning values to properties of JavaBean objects
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor descriptor : propertyDescriptors) {
            String propertyName = descriptor.getName();
            if (map.containsKey(propertyName)) {
                Object value = map.get(propertyName);
                if (value == null) continue;
                try {
                    Object[] args;
                    if (value instanceof String) {
                        args = new Object[]{convert(descriptor.getPropertyType(), (String) value)};
                    } else {
                        args = new Object[]{value};
                    }
                    descriptor.getWriteMethod().invoke(obj, args);
                } catch (Exception ignored) {
                }
            }
        }
        return obj;
    }

    /**
     * Convert string to target type
     */
    public static Object convert(Class type, String s) {
        if (type == String.class) {
            return s;
        }
        if (type.isPrimitive()) {
            if (type == byte.class) {
                return Byte.valueOf(s);
            } else if (type == short.class) {
                return Short.valueOf(s);
            } else if (type == int.class) {
                return Integer.valueOf(s);
            } else if (type == long.class) {
                return Long.valueOf(s);
            } else if (type == boolean.class) {
                return Boolean.valueOf(s);
            } else if (type == float.class) {
                return Float.valueOf(s);
            } else if (type == double.class) {
                return Double.valueOf(s);
            } else {
                throw new IllegalArgumentException("cannot convert string to " + type);
            }
        }
        if (type == Date.class) {
            return new Date(java.sql.Date.valueOf(s).getTime());
        } else {
            throw new IllegalArgumentException("cannot convert string to " + type);
        }
    }

    public static <T> T convertBean(Class<T> type, Object src) throws Exception {
        if (src == null)
            return null;

        return toBean(type, toMap(src));
    }

    public static <T> List<T> convertBeans(Class<T> type, Collection<?> srcList) throws Exception {
        return convertBeans(type, srcList, false);
    }

    public static <T> List<T> convertBeans(Class<T> type, Collection<?> srcList, boolean emptyIfNull) throws Exception {
        if (srcList == null)
            return emptyIfNull ? new ArrayList<T>(0) : null;

        List<T> targetList = new ArrayList<>(srcList.size());
        for (Object src : srcList) {
            targetList.add(convertBean(type, src));
        }
        return targetList;
    }

    /**
     * Converting a JavaBean object into a map
     */
    private static Map<String, Object> toMap(Object bean, boolean remainNullProperty)
            throws IntrospectionException, IllegalAccessException, InvocationTargetException {
        Class type = bean.getClass();
        Map<String, Object> returnMap = new HashMap<String, Object>();
        BeanInfo beanInfo = Introspector.getBeanInfo(type);

        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor descriptor : propertyDescriptors) {
            String propertyName = descriptor.getName();
            if (!propertyName.equals("class")) {
                Method readMethod = descriptor.getReadMethod();
                Object result = readMethod.invoke(bean);
                if (result != null) {
                    returnMap.put(propertyName, result);
                } else {
                    if (remainNullProperty) {
                        returnMap.put(propertyName, null);
                    } else {
                        returnMap.put(propertyName, "");
                    }
                }
            }
        }
        return returnMap;
    }

    public static Map<String, Object> toMap(Object bean)
            throws IntrospectionException, IllegalAccessException, InvocationTargetException {
        return toMap(bean, true);
    }

    public static Map<String, Object> toMapReplaceNull(Object bean)
            throws IntrospectionException, IllegalAccessException, InvocationTargetException {
        return toMap(bean, false);
    }

    /**
     * Convert the object and give the type identification of the attribute
     * int short long -> i
     * float -> f
     * java.util.Date java.sql.Date -> d
     * otherwise t
     */
    public static Map<String, Pair<String, Object>> toMapAndValueType(Object bean)
            throws IntrospectionException, IllegalAccessException, InvocationTargetException {
        Class type = bean.getClass();
        Map<String, Pair<String, Object>> returnMap = new HashMap<String, Pair<String, Object>>();
        BeanInfo beanInfo = Introspector.getBeanInfo(type);

        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor descriptor : propertyDescriptors) {
            String propertyName = descriptor.getName();
            if (!propertyName.equals("class")) {
                Method readMethod = descriptor.getReadMethod();
                Object result = readMethod.invoke(bean);
                if (result == null) {
                    continue;
                }
                Class typeClass = descriptor.getPropertyType();
                String typeName = typeClass.getSimpleName();
                String prefix = "t";
                if ("String".equals(typeName)) {
                } else if (typeClass.isPrimitive()) {
                    if ("int".equals(typeName) || "short".equals(typeName) || "long".equals(typeName)) {
                        prefix = "i";
                    } else if ("float".equals(typeName)) {
                        prefix = "f";
                    }
                } else {
                    if ("Integer".equals(typeName) || "Short".equals(typeName) || "Long".equals(typeName)) {
                        prefix = "i";
                    } else if ("Float".equals(typeName)) {
                        prefix = "f";
                    } else if (result instanceof Date || result instanceof java.sql.Date) {
                        Date date = (Date) result;
                        result = date.getTime();
                        prefix = "d";
                    }
                }
                returnMap.put(propertyName, Pair.build(prefix, result));
            }
        }
        return returnMap;
    }
}
